﻿namespace CacheDependencyManagerWindowsService
{
    #region N A M E S P A C E   I M P O R T S

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using CacheProvider;
    using CacheProvider.AppFabric;
    using CacheProviderEntities;
    using CacheProviderInterfaces;
    using CacheProviderUtilities;
    using Microsoft.ApplicationServer.Caching;
    using Microsoft.Practices.Composite.Presentation.Events;

    #endregion

    public class CacheDependencyProcessor
    {
        #region P R I V A T E   A T T R I B U T E S

        private string _dependenciesCacheKey;

        const DataCacheOperations NotificationFilters = DataCacheOperations.AddItem |
                                                        DataCacheOperations.RemoveItem |
                                                        DataCacheOperations.ReplaceItem;

        private readonly SqlMonitoringManager _sqlMonitoringManager;

        private DataCacheNotificationDescriptor _dataCacheNotificationDescriptor;

        private readonly List<DataCacheNotificationDescriptor> _dataCacheNotificationDescriptors;

        private readonly AppFabricCacheProvider _appFabricCacheProvider;

        private readonly ILogWriter _logWriter;

        private Int32 _dependencyNotificationProcessingDelayInSeconds;

        #endregion

        #region C O N S T R U C T O R ( S )

        public CacheDependencyProcessor()
        {
            this._dataCacheNotificationDescriptors = new List<DataCacheNotificationDescriptor>();

            this._appFabricCacheProvider = CacheBroker.Provider as AppFabricCacheProvider;

            Debug.Assert(_appFabricCacheProvider != null, "Appfabric must be the default provider configured in the configuration file");

            this._logWriter = new EntLibLogWriter();

            this._sqlMonitoringManager = new SqlMonitoringManager(_appFabricCacheProvider.DIContainer);
        }

        #endregion

        #region P U B L I C   M E T H O DS

        public bool StartMonitoringDepdencies(string dependenciesCacheKey, int dependencyNotificationProcessingDelayInSeconds)
        {
            this._dependenciesCacheKey = dependenciesCacheKey;
            this._dependencyNotificationProcessingDelayInSeconds = dependencyNotificationProcessingDelayInSeconds;

            _dataCacheNotificationDescriptor = this._appFabricCacheProvider.AddItemLevelCallback(dependenciesCacheKey,
                                                                                                 NotificationFilters,
                                                                                                 ClientNodesNotificationCallback,
                                                                                                 string.Empty);
            this._dataCacheNotificationDescriptors.Add(_dataCacheNotificationDescriptor);

            //this.ProcessDependencies();
            this.ProcessCacheClientNodesCollection();

            const string message = "Cache Dependency Monitor Windows Service Started Monitoring";
            this._logWriter.Write(message, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc, TraceEventType.Information, "StartMonitoringDepdencies()");

            return true;
        }

        public void StopMonitoring()
        {
            if (this._dataCacheNotificationDescriptors != null)
            {
                foreach (var dataCacheNotificationDescriptor in _dataCacheNotificationDescriptors)
                {
                    this._appFabricCacheProvider.RemoveCallback(dataCacheNotificationDescriptor);
                }
            }
            //if (this._dataCacheNotificationDescriptor != null)
            //    this._appFabricCacheProvider.RemoveCallback(this._dataCacheNotificationDescriptor);

            var cacheDependencyTrackers = _appFabricCacheProvider.Get(this._dependenciesCacheKey) as List<CacheDependencyTracker>;

            if (cacheDependencyTrackers != null)
                foreach (var dependencyTracker in cacheDependencyTrackers)
                {
                    this._sqlMonitoringManager.RemoveSqlDependency(dependencyTracker.Key, dependencyTracker.NamedCache,
                                                                   dependencyTracker.RegionName, null);
                }

            const string message = "Cache Dependency Monitor Windows Service Stopped";
            this._logWriter.Write(message, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc, TraceEventType.Information, "StopMonitoring()");
        }

        #endregion

        #region P R I V A T E   M E T H O D S

        private void ClientNodesNotificationCallback(string cacheName, string regionName, string key,
                                           DataCacheItemVersion version, DataCacheOperations cacheOperation,
                                           DataCacheNotificationDescriptor notificationDescriptor)
        {
            this.ProcessCacheClientNodesCollection();
        }

        private void ProcessCacheClientNodesCollection()
        {
            var appFabricCacheClientInfos = this._appFabricCacheProvider.Get<List<AppFabricCacheClientInfo>>(this._dependenciesCacheKey);
            if (appFabricCacheClientInfos == null)
                return;

            var clientsThatExited = from clientInfo in appFabricCacheClientInfos
                                    where clientInfo.IsClientAlive == false
                                    select clientInfo;

            this.ProcessClosedClients(clientsThatExited);

            foreach (var appFabricCacheClientInfo in appFabricCacheClientInfos.Where(client => client.IsClientAlive))
            {
                var message = string.Format("Cache notification callback added for node {0}", appFabricCacheClientInfo.ClientIdentifier);
                var dataCacheNotificationDescriptor = this._appFabricCacheProvider.AddItemLevelCallback(
                                                                            appFabricCacheClientInfo.ClientIdentifier,
                                                                            NotificationFilters,
                                                                            DependencyTrackerListChangeNotificationCallback,
                                                                            string.Empty);
                this._dataCacheNotificationDescriptors.Add(dataCacheNotificationDescriptor);

                if (this._logWriter != null)
                    this._logWriter.Write(message, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc,
                                          TraceEventType.Information, "AppFabricWindowsService");


                ThreadPool.QueueUserWorkItem(this.ProcessDependencies, appFabricCacheClientInfo.ClientIdentifier);
            }
        }

        /// <summary>
        /// For AppFabric Cache Clients that have exited, Get its
        /// Dependency Tracker list and update the Delay in processing to immediate
        /// for any data change notification.
        /// </summary>
        /// <param name="closedClients"></param>
        private void ProcessClosedClients(IEnumerable<AppFabricCacheClientInfo> closedClients)
        {
            foreach (var closedClient in closedClients)
            {
                var cacheDependencyTrackers = this._appFabricCacheProvider.Get(closedClient.ClientIdentifier) as List<CacheDependencyTracker>;
                if (cacheDependencyTrackers != null)
                {
                    foreach (var dependencyTracker in cacheDependencyTrackers)
                    {
                        this._sqlMonitoringManager.UpdateDataChangeProcessingDelay(dependencyTracker.Key,
                                                                                   dependencyTracker.NamedCache,
                                                                                   dependencyTracker.RegionName,
                                                                                   TimeSpan.Zero);
                    }
                }
            }
        }


        /// <summary>
        /// This callback is invoked by AppFabric if 
        /// the CacheDependencyTracker list changes for a Cache Client Node.
        /// </summary>
        /// <param name="cacheName"></param>
        /// <param name="regionName"></param>
        /// <param name="key"></param>
        /// <param name="version"></param>
        /// <param name="cacheOperation"></param>
        /// <param name="notificationDescriptor"></param>
        private void DependencyTrackerListChangeNotificationCallback(string cacheName, string regionName, string key,
                                                   DataCacheItemVersion version, DataCacheOperations cacheOperation,
                                                   DataCacheNotificationDescriptor notificationDescriptor)
        {
            if (cacheOperation == DataCacheOperations.AddItem
                || cacheOperation == DataCacheOperations.RemoveItem
                || cacheOperation == DataCacheOperations.ReplaceItem)
            {
                ProcessDependencies(key);
            }
        }

        private void ProcessDependencies(object key)
        {
            try
            {
                var cacheDependencyTrackers = _appFabricCacheProvider.Get(key.ToString()) as List<CacheDependencyTracker>;

                if (cacheDependencyTrackers != null)
                    foreach (var dependencyTracker in cacheDependencyTrackers)
                    {
                        var dependencyInfo = dependencyTracker.DependencyData;

                        if (dependencyInfo.CacheDependencyType != CacheDependencyTypes.SQLDependency) continue;

                        if (dependencyInfo.ISDbCommandBased)
                        {
                            CacheDBCommandHelper.RestoreDBCommandParameters(dependencyInfo);
                        }

                        var status = _sqlMonitoringManager.StartMonitoring(dependencyInfo.DBConnectionString);

                        if (status != CacheDependencyStatus.MonitoringStarted
                            && status != CacheDependencyStatus.AlreadyMonitoring)
                        {
                            var message = "Could not start SQL Dependency Monitoring In Cache Dependency Monitor Windows Service!";
                            this._logWriter.Write(message, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc, TraceEventType.Critical, "AppFabricWindowsService");
                            return;
                        }

                        this._sqlMonitoringManager.AddSqlDependencies<Object>(
                                                                        dependencyTracker.Key, dependencyTracker.NamedCache,
                                                                        dependencyTracker.RegionName, dependencyInfo, false, null,
                                                                        null, ThreadOption.PublisherThread, false,
                                                                         TimeSpan.FromSeconds(this._dependencyNotificationProcessingDelayInSeconds),
                                                                        dependencyTracker.CacheItemDataVersion);
                    }

                if (this._logWriter != null)
                {
                    var logMessage = string.Format("Added SQL dependency Monitoring for all CacheDependencyTrackers under CacheItemKey:{0} by ProcessDependencies method", key);
                    this._logWriter.Write(logMessage, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc, TraceEventType.Information, "AppFabricWindowsService");
                }
            }
            catch (Exception exception)
            {
                if (this._logWriter != null)
                    this._logWriter.Write(exception.Message, "TomcatLogging", 1, (int)CacheOperation.DependencyMonitorSvc, TraceEventType.Critical, "AppFabricWindowsService");
            }
        }

        #endregion
    }
}
